<?php

namespace fakis\core\traits;

use fakis\core\base\DefineModel;

/**
 * 绑定定义模型
 *
 * 开发定义模型的初忠:
 * 主要为了方便在模型的属性有下级模型的验证处理，如果有下级模型的模型属性都要处理一次，后面增加多了流程就越麻烦了
 * 为了解决这一痛点，而开发的功能
 * 初步命名为`定义模型`，详细可查看 `\fakis\core\base\DefineModel`
 *
 * @see \fakis\core\base\DefineModel 定义模型
 *
 * 在模型中使用：
 * ```php
 * public function bindDefineModels()
 * {
 *     return [
 *         'field1' => 'jsonData',
 *         'field2' => function ($model) {
 *             $define = new DefineModel();
 *             $define->props = [];
 *             $define->populate($model->getAttribute('field1'));
 *             return $define;
 *         }
 *     ];
 * }
 *
 * public function getJsonData()
 * {
 *     $define = new DefineModel();
 *     $define->props = [];
 *     // 必须使用 `$model->getAttribute()` 方法来输入属性值
 *     $define->populate($this->getAttribute('field1'));
 *     return $define;
 * }
 *
 * // 用于验证规则
 * public function rules()
 * {
 *     return [
 *         [['field1'], \fakis\core\validators\DefineModelValidator::class],
 *         [['field1', 'field2'], \fakis\core\validators\DefineModelValidator::class, 'bind' => ['field1', 'field2']],
 *     ];
 * }
 * ```
 *
 * @property-read DefineModel[] $defineModels
 *
 * @author Fakis <fakis738@qq.com>
 */
trait ModelDefine
{
    /**
     * @var DefineModel[]
     */
    private $_definedModels = [];

    /**
     * 绑定定义模型
     * @return array
     */
    public function bindDefineModels()
    {
        return [];
    }

    /**
     * 返回指定已绑定的定义模型是否存在
     * @param string $name
     * @return bool
     */
    public function hasDefinedModel($name)
    {
        return $this->getDefinedModel($name) instanceof DefineModel;
    }

    /**
     * 返回所有已绑定的定义模型
     * @return DefineModel[]
     */
    public function getDefinedModels()
    {
        return $this->_definedModels;
    }

    /**
     * 返回指定已绑定的定义模型
     * @param string $name
     * @return DefineModel|null
     */
    public function getDefinedModel($name)
    {
        if (isset($this->_definedModels[$name])) {
            return $this->_definedModels[$name];
        }

        $model = null;
        $defineModels = $this->bindDefineModels();
        if (isset($defineModels[$name])) {
            $value = $defineModels[$name];
            if (is_string($value) && $this->canSetProperty($value)) {
                $model = $this->$value;
            } elseif (is_callable($value)) {
                $model = $value($this);
            }
        }

        if ($model instanceof DefineModel) {
            return $this->_definedModels[$name] = $model;
        }
        return null;
    }

    /**
     * 清除已绑定的定义模型
     */
    public function clearDefinedModels()
    {
        $this->_definedModels = [];
    }

    /**
     * 重写此方法，以便绑定的定义模型也可以像访问属性一样
     * @inheritDoc
     */
    public function __get($name)
    {
        if ($this->hasDefinedModel($name)) {
            $model = $this->getDefinedModel($name);
            if ($this->canSetProperty($name)) {
                $model->load(parent::__get($name));
            }
            return $model->attributes;
        }

        return parent::__get($name);
    }

    /**
     * 重写此方法，以便绑定的定义模型也可以像访问属性一样
     * @inheritDoc
     */
    public function __set($name, $value)
    {
        if ($this->hasDefinedModel($name)) {
            $this->getDefinedModel($name)->load($value);
            $this->canSetProperty($name) && parent::__set($name, $this->getDefinedModel($name)->attributes);
            return;
        }

        parent::__set($name, $value);
    }
}